package gripper.ftp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import gripper.ftp.handle.Handle;
import gripper.ftp.info.FixSizeLinkedList;
import gripper.ftp.info.ServerInfo;
import gripper.ftp.prop.User;

public class FtpSession implements Runnable, Comparable<FtpSession> {
	private Object dataSockLock = new Object();
	private long id = -1;
	private FtpServer ftpServer;
	private Socket cmdSocket;
	private Socket dataSocket;
	private Thread sessionThread;
	Map<CmdType, Handle> handleMap;
	private ServerSocket dataSocketServer;
	public String currentDir;
	public boolean running;
	private String userName;
	private String rootDir;
	private ExecutorService executorService;
	public AtomicBoolean abort = new AtomicBoolean(false);
	public String fileNameRNFR;
	public CmdType waitingCmd = null;
	private TransMode transMode = TransMode.NULL;
	private FixSizeLinkedList<String> cmdList;
	Random rand = new Random(System.currentTimeMillis());
	private String clientIP;
	private int clientPort;
	private long startTime;
	ServerInfo serverInfo;

	public FtpSession(FtpServer ftpServer, Socket cmdSocket, ServerInfo serverInfo) {
		id = System.currentTimeMillis() << 3 + System.nanoTime();
		this.serverInfo = serverInfo;
		cmdList = new FixSizeLinkedList<>(1024);
		this.ftpServer = ftpServer;
		running = true;
		this.cmdSocket = cmdSocket;
		sessionThread = new Thread(this);
		handleMap = new HashMap<CmdType, Handle>();
		currentDir = "/";
		executorService = Executors.newCachedThreadPool();
		initHandle();
		startTime = System.currentTimeMillis();
	}

	public String getIp() {
		return cmdSocket.getInetAddress().getHostAddress();
	}

	private void initHandle() {
		for (CmdType ct : CmdType.values()) {
			try {
				Class<?> clazz = Class.forName("gripper.ftp.handle." + ct.name() + "Handle");
				Constructor<?> constructor = clazz.getConstructor(FtpSession.class);
				Handle handle = (Handle) constructor.newInstance(this);
				handleMap.put(ct, handle);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}

	public void work() {
		sessionThread.start();
	}

	public void run() {
		try {
			serverInfo.addTryLog();
			InputStream inputStream = cmdSocket.getInputStream();
			OutputStream os = cmdSocket.getOutputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
			os.write("220 Service Ready\r\n".getBytes());
			os.flush();
			String userNameLine = null;
			while (true) {
				String firstLine = br.readLine();
				if (firstLine != null && firstLine.startsWith("HELP")) {
					Handle handle = handleMap.get(CmdType.HELP);
					handle.process(firstLine, br, os);
					continue;
				}
				userNameLine = firstLine;
				break;
			}
			os.write("331 User name okay, need password.\r\n".getBytes());
			os.flush();
			String passLine = br.readLine();
			if (!validUser(userNameLine, passLine)) {
				os.write("530 Not logged in.\r\n".getBytes());
				os.flush();
				os.close();
				cmdSocket.close();
				return;
			}
			os.write("230 User logged in\r\n".getBytes());
			os.flush();
			serverInfo.addLoged();
			rootDir = ftpServer.propManager.usersMap.get(userName).getRootDir();
			while (running) {
				handleCmd(br, os);
			}

		} catch (Exception e) {
			this.close();
		}
	}

	private boolean validUser(String userNameLine, String passLine) throws IOException {
		if (userNameLine == null || passLine == null) {
			return false;
		}
		if (!userNameLine.toUpperCase().startsWith("USER") || !passLine.toUpperCase().startsWith("PASS")) {
			return false;
		}
		String[] slited = userNameLine.split(" ");
		if (slited == null || slited.length < 2) {
			return false;
		}
		if (slited[1].trim().length() <= 0) {
			return false;
		}
		if (!ftpServer.propManager.usersMap.containsKey(slited[1])) {
			return false;
		}
		userName = slited[1];
		User user = ftpServer.propManager.usersMap.get(userName);
		String[] slitedPass = passLine.split(" ");
		if (slitedPass == null || slitedPass.length < 2) {
			return false;
		}
		if ("anoymous".equalsIgnoreCase(userName)) {
			return true;
		}
		String pass = slitedPass[1];
		if (!user.getPass().equals(pass)) {
			return false;
		}
		return true;
	}

	private void handleCmd(BufferedReader br, OutputStream os) throws IOException {
		String line = br.readLine();
		if (line == null) {
			this.close();
			return;
		}
		System.out.println(line);
		CmdType cmdType = parseCmd(line);
		if (cmdType == null) {
			os.write("202 Command not implemented, superfluous at this site. \r\n".getBytes());
			os.flush();
			this.close();
			return;
		}
		cmdList.add(cmdType.name());
		Handle handle = handleMap.get(cmdType);
		handle.process(line, br, os);
	}

	private CmdType parseCmd(String line0) {
		String line = line0.toUpperCase().trim();
		String[] splited = line.split(" ");
		String head = splited[0];
		if (head == null) {
			return null;
		}
		try {
			CmdType ct = CmdType.valueOf(head);
			return ct;
		} catch (Exception e) {
			return null;
		}
	}

	public int createDataServer() throws IOException {
		for (int x = 1024; x < 65535; ++x) {
			try {
				int port = rand.nextInt(65534 - 1024);
				port = port + 1024;
				ServerSocket s = new ServerSocket(port);
				dataSocketServer = s;
				break;
			} catch (Exception e) {
				continue;
			}
		}
		return dataSocketServer.getLocalPort();
	}

	public class DataWorker implements Runnable {
		FtpSession ftpSession;

		public DataWorker(FtpSession ftpSession) {
			this.ftpSession = ftpSession;
		}

		public void run() {
		}

	}

	public void close() {
		this.cmdList.clear();
		try {
			if (cmdSocket != null) {
				cmdSocket.close();
				cmdSocket = null;
			}
			if (dataSocketServer != null) {
				dataSocketServer.close();
				dataSocketServer = null;
			}

		} catch (Exception e) {
		}
		running = false;
		ftpServer.remove(this);
	}

	@Override
	public int compareTo(FtpSession o) {
		return (int) (id - o.id);
	}

	public void ansyAccept() {
		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					synchronized (FtpSession.this.dataSockLock) {
						FtpSession.this.dataSocket = FtpSession.this.dataSocketServer.accept();
					}
				} catch (Exception e) {
					if (FtpSession.this.dataSocketServer != null) {
						try {
							FtpSession.this.dataSocketServer.close();
						} catch (IOException e1) {
						}
					}
					if (FtpSession.this.dataSocket != null) {
						try {
							FtpSession.this.dataSocket.close();
						} catch (IOException e1) {
						}
					}
					FtpSession.this.dataSocketServer = null;
				} finally {
				}
			}
		});
	}

	public void ansyConnect(final String ip, final int port) {
		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					Socket sock = new Socket();
					sock.connect(new InetSocketAddress(ip, port));
					FtpSession.this.dataSocket = sock;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
	}


	public void setDataSocketServer(ServerSocket dataSocketServer) {
		this.dataSocketServer = dataSocketServer;
	}

	public DataSocket getClientSock() {
		try {
			if (this.transMode == TransMode.NULL) {
				if (this.clientIP != null) {
					this.dataSocket = new Socket();
					dataSocket.connect(new InetSocketAddress(clientIP, clientPort));
					return new DataSocket(this, dataSocket);
				} else {
					return null;
				}
			} else if (this.transMode == TransMode.PORT) {
				for (int i = 0; i < 100; ++i) {
					if (this.dataSocket != null) {
						return new DataSocket(this, dataSocket);
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
					}
				}
			} else {
				synchronized (FtpSession.this.dataSockLock) {
					if (this.dataSocket != null) {
						return new DataSocket(this, dataSocket);
					}
					dataSocket = this.dataSocketServer.accept();
				}
				return new DataSocket(this, dataSocket);
			}
		} catch (IOException e) {
		}
		return null;
	}

	public void setClientInfo(String ip, int port) {
		this.clientIP = ip;
		this.clientPort = port;
	}

	public <T> void submit(Callable<T> c) {
		executorService.submit(c);
	}

	public TransMode getTransMode() {
		return transMode;
	}

	public void setTransMode(TransMode transMode) {
		this.transMode = transMode;
	}

	public String getRootDir() {
		return rootDir;
	}

	public void setRootDir(String rootDir) {
		this.rootDir = rootDir;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getClientIp() {
		if (cmdSocket != null && cmdSocket.isConnected()) {
			return cmdSocket.getInetAddress().getHostAddress();
		}
		return null;
	}

	public Date getStartTime() {
		return new Date(startTime);
	}

	public List<String> getCmdList() {
		return cmdList.getAll();
	}

	public long getSessionId() {
		return this.id;
	}
}
